<?php

namespace App\Service\Cruise\Mission;

use App\Enums\BaseErrorCode;
use App\Exceptions\Custom\ResponseHttpException;
use App\Models\Common\ResourcesModel;
use App\Models\Cruise\CruiseCheckAlbumModel;
use App\Models\Cruise\CruiseCheckRiskProblemModel;
use App\Models\Cruise\CruiseOrderInputModel;
use App\Models\Cruise\CruiseOrderModel;
use App\Models\Cruise\CruiseOrderInputTypeModel;
use App\Models\Cruise\CruiseTaskProjectModel;
use App\Models\Cruise\CruiseTemplateModel;
use App\Models\Cruise\CruiseTemplatePositionModel;
use App\Models\Cruise\CruiseTemplatePositionProjectMappingModel;
use App\Models\Kms\Album\AlbumResourceModel;
use App\Models\Workflow\WorkflowOrderModel;
use App\Models\Workflow\WorkflowOrderNodeModel;
use App\Remote\Rms\RmsShopRemote;
use App\Service\BaseService;
use App\Service\Renew\RenewService;
use App\Service\Workflow\WorkflowOrderService;
use App\Support\CustomForm\AttributeCruiseSupport;
use App\Support\CustomForm\AttributeSupport;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;

abstract class CruiseOrderInputBaseService extends BaseService
{
    protected string            $orderId;
    protected int               $templateType;
    protected int               $sysTemplate;
    protected ?CruiseOrderModel $baseCruiseOrderModel;

    protected WorkflowOrderModel   $workflowOrderModel;
    protected WorkflowOrderService $workflowOrderService;

    protected WorkflowOrderNodeModel $workflowOrderNodeModel;

    protected function __construct(
        string $orderId,
        int $templateType,
        int $sysTemplate,
        ?CruiseOrderModel $cruiseOrderModel = null
    ) {
        $this->orderId      = $orderId;
        $this->templateType = $templateType;
        $this->sysTemplate  = $sysTemplate;

        $this->baseCruiseOrderModel = $cruiseOrderModel;
    }

    private function getBaseCruiseOrderModel(): ?CruiseOrderModel
    {
        if (!$this->baseCruiseOrderModel && $this->orderId) {
            $this->baseCruiseOrderModel = CruiseOrderModel::query()->find($this->orderId);
        }

        return $this->baseCruiseOrderModel;
    }

    /**
     * 初始化表单信息
     * @return void
     */
    abstract protected function initOrderInput(): void;

    /**
     * 保存用户提交单个字段
     * @param string $field
     * @param $value
     * @return void
     */
    abstract protected function saveUserContentJson(string $field, $value): void;

    /**
     * 获取表单回显信息
     * @return array
     */
    abstract protected function getInputObjectFill(): array;

    /**
     * 表单最终提交
     * @return void
     */
    abstract protected function submitOrder(): void;

    /**
     * 表单审核
     * @param array $data
     * @return void
     */
    abstract protected function audit(array $data): void;

    /**
     * projectId->field的map
     * @var array
     */
    protected array $projectId_field_mapping = [];

    public function setWorkflowOrder(WorkflowOrderModel $workflowOrderModel): self
    {
        $this->workflowOrderModel   = $workflowOrderModel;
        $this->workflowOrderService = new WorkflowOrderService($workflowOrderModel->id, $this->userId);
        return $this;
    }

    public function setWorkflowOrderNo(string $workflowNo): self
    {
        $workflowOrderModel = WorkflowOrderModel::query()->where('workflowNo', $workflowNo)->first();
        $this->setWorkflowOrder($workflowOrderModel);

        return $this;
    }

    public function setWorkflowOrderNode(WorkflowOrderNodeModel $workflowOrderNodeModel): self
    {
        $this->workflowOrderNodeModel = $workflowOrderNodeModel;
        return $this;
    }

    /**
     * 初始化模板下所有项目
     * @return string
     */
    protected function initInputObjectJson(int $templateId): string
    {
        $template = CruiseTemplateModel::query()->find($templateId);
        if (!$template) {
            throw new ResponseHttpException(BaseErrorCode::$ERROR_OCCURRED, null,
                "获取模板失败:{$templateId}");
        }

        $this->projectId_field_mapping = [];

        return $template->positionsRel->map(function ($position) {
            return [
                'positionName' => $position->name,
                'projects'     => $position->positionProjectMappingsRel->map(function ($mapping) {
                    return $this->buildProjects($mapping);
                })->filter()->values()->toArray(),
            ];
        })->toJson(JSON_UNESCAPED_UNICODE);
    }

    /**
     * 字段默认值
     * @var array
     */
    private array $fieldDefaultValue = [];

    /**
     * 命中需要记录cruise_order_modules的巡检项的inputType
     * @var array
     */
    private array $hitInputTypes = [];

    protected function buildProjects(CruiseTemplatePositionProjectMappingModel $mapping): ?string
    {
        $projectModel = $mapping->projectRel;
        if ($projectModel->status == CruiseTaskProjectModel::STATUS_OFF) {
            return null;
        }

        if (in_array($projectModel->inputType, CruiseOrderInputTypeModel::SUPPORT_INPUT_TYPES)) {
            $this->hitInputTypes[] = $projectModel->inputType;
        }

        $inputObject = unserialize($projectModel->inputObject);

        // 如果关联了后面的字段，这里就会丢失relations
        $this->projectId_field_mapping[$mapping->projectId] = $inputObject->getInputName();

        // 添加tips
        if ($projectModel->tipsType == AttributeSupport::INPUT_TYPE_STRING) {
            $inputObject->setCustomAttribute(["desc", $projectModel->tipsContent]);
        } elseif ($projectModel->tipsType == AttributeSupport::INPUT_TYPE_IMG_GROUP) {

            // 资源素材
            $resource = (array)ResourcesModel::getModuleBusinessResources(ResourcesModel::MODULE_TYPE_CRUISE,
                ResourcesModel::BUSINESS_CRUISE_PROJECT_TIPS, $projectModel->id, ['tipsResource']);
            $inputObject->setCustomAttribute(["descImg", $resource['tipsResource']]);
        } elseif ($projectModel->tipsType == AttributeSupport::INPUT_TYPE_STRING_IMG_GROUP) {

            // 资源素材
            $resource = (array)ResourcesModel::getModuleBusinessResources(ResourcesModel::MODULE_TYPE_CRUISE,
                ResourcesModel::BUSINESS_CRUISE_PROJECT_TIPS, $projectModel->id, ['tipsResource']);

            $inputObject->setCustomAttribute(["desc", $projectModel->tipsContent])
                    ->setCustomAttribute(["descImg", $resource['tipsResource']]);
        }

        // 提示类型
        $inputObject->setCustomAttribute(["tipsType", $projectModel->tipsType]);

        // relations
        if ($mapping->relationProjectId) {
            $relationField = $this->projectId_field_mapping[$mapping->relationProjectId] ?? '';
            $inputObject->setRelations([$relationField, json_decode($mapping->relationOptions, true)]);
        }

        // 默认值
        $defaultValue = $inputObject->customAttributes['defaultValue'] ?? null;
        if ($defaultValue !== null) {
            $this->fieldDefaultValue[$inputObject->inputName] = $defaultValue;
        }

        return serialize($inputObject);
    }

    protected function baseInitOrderInput(int $templateId)
    {
        // 选址房源做烂了，templateType=2，用的是site_locations的id
        $input = CruiseOrderInputModel::query()->updateOrCreate([
            'orderId'      => $this->orderId,
            'templateType' => $this->templateType
        ], [
            'inputObjectJson' => $this->initInputObjectJson($templateId),
            'userContentJson' => json_encode($this->fieldDefaultValue),
            'sysTemplate'     => $this->sysTemplate,
        ]);

        // 支持组件
        if ($this->hitInputTypes) {
            $hits = collect($this->hitInputTypes)->unique()->values()->map(function ($item) {
                return [
                    'orderId'   => $this->orderId,
                    'inputType' => $item,
                    'templateType' => $this->templateType,
                ];
            })->toArray();

            CruiseOrderInputTypeModel::query()->insert($hits);
        }

        return $input;
    }

    /**
     * 忽略 - 不对该字段做save
     */
    const IGNORE_SAVE_VALUE       = 'IGNORE_SAVE_VALUE';
    const IGNORE_SAVE_AUDIT_VALUE = ['IGNORE_SAVE_AUDIT_VALUE'];

    /**
     * 保存用户填写内容
     * @param string $field
     * @param $value
     * @param array $auditValue
     * @param \Closure $closure 自定义的匿名函数，调整$userContentJsonArr
     * @return void
     */
    protected function baseSaveUserContentJson(
        string $field,
        $value,
        array $auditValue = [],
        ?\Closure $closure = null
    ): void {

        $id = CruiseOrderInputModel::query()
                                   ->where('orderId', $this->orderId)
                                   ->where('sysTemplate', $this->sysTemplate)
                                   ->value('id');

        DB::beginTransaction();

        // 锁死-防止多个进程同时修改userContentJson
        $cruiseOrderInputModel = CruiseOrderInputModel::query()
                                                      ->where('id', $id)
                                                      ->lockForUpdate()
                                                      ->first();

        // 校验字段是否存在
        list($inputType, $meanType, $projectObj) = $this->checkField($cruiseOrderInputModel, $field);

        // 生物防治，字段校验&处理
        if ($inputType == AttributeCruiseSupport::INPUT_TYPE_CRUISE_CHECK_BIOLOGY) {
            $auditValue = $this->biologyValidateAndHandle($value, $auditValue, $meanType);
        }

        // 巡检项，字段校验
        if ($inputType == AttributeCruiseSupport::INPUT_TYPE_CRUISE_CHECK) {
            $this->cruiseCheckValidate($value, $auditValue);
        }

        // 巡检项-不适用，字段校验
        if ($inputType == AttributeCruiseSupport::INPUT_TYPE_CRUISE_CHECK_SKIP && $value != CheckInputService::IGNORE_SAVE_VALUE && $value !== null) {

            if ($value == AttributeCruiseSupport::CRUISE_CHECK_FAIL) {
                BaseService::throwErr("自检项，不能选择不合格");
            }

            $options = $projectObj->getCustomAttribute()['options'] ?? [];
            $findValue = collect($options)->where('value', $value)->count();
            if (!$findValue) {
                BaseService::throwErr("不存在的值：{$value}");
            }
        }

        $userContentJsonArr = $cruiseOrderInputModel->userContentJsonArr;

        if (in_array($inputType, [AttributeSupport::INPUT_TYPE_IMG_GROUP, AttributeSupport::INPUT_VIDEO])) {

            if ($value !== self::IGNORE_SAVE_VALUE) {

                // 素材
                $resources = collect($value)->map(function ($item) {
                    $item = ltrim(parse_url($item, PHP_URL_PATH), '/');
                    return $item;
                })->toArray();

                list($module, $business) = CruiseTemplateModel::TYPE_RESOURCE_PARAM[$this->templateType];
                ResourcesModel::moduleBusinessSingleResourcesUpdate(
                    $module,
                    $business,
                    $field,
                    $this->orderId,
                    $resources,
                    1
                );
            }

        } else {

            if (!$userContentJsonArr) {
                $userContentJsonArr = [];
            }

            if ($value !== self::IGNORE_SAVE_VALUE) {
                $userContentJsonArr[$field] = $value;
            }

            if ($value === null) {
                unset($userContentJsonArr[$field]);
            }

            if ($auditValue != self::IGNORE_SAVE_AUDIT_VALUE) {

                // 保存auditValue字段
                $auditField = $field . '_auditValue';
                if ($auditValue) {
                    $userContentJsonArr[$auditField] = $auditValue;
                } else {
                    unset($userContentJsonArr[$auditField]);
                }
            }

            if ($closure) {
                $userContentJsonArr = $closure($userContentJsonArr);
            }

            $cruiseOrderInputModel->userContentJson = json_encode($userContentJsonArr, true);

            $cruiseOrderInputModel->save();
        }

        DB::commit();
    }

    private function checkField(CruiseOrderInputModel $cruiseOrderInputModel, string $field): array
    {
        // 校验字段是否存在
        foreach ($cruiseOrderInputModel->inputObjectJsonArr as $projects) {
            foreach ($projects['projects'] as $project) {
                $projectObj = unserialize($project);
                if ($projectObj->getInputName() == $field) {
                    $inputType = $projectObj->getInputType();
                    $meanType  = $projectObj->getMeanType();

                    return [$inputType, $meanType, $projectObj];
                }
            }
        }

        DB::rollBack();

        throw new ResponseHttpException(BaseErrorCode::$ERROR_OCCURRED, null, "不存在的field:{$field}");
    }

    /**
     * 更新督导二次审核字段
     * @param string $field
     * @return void
     */
    protected function saveAuditSecond(string $field, int $value, string $instructions)
    {
        $id = CruiseOrderInputModel::query()
                                   ->where('orderId', $this->orderId)
                                   ->where('sysTemplate', $this->sysTemplate)
                                   ->value('id');

        DB::beginTransaction();

        // 锁死-防止多个进程同时修改userContentJson
        $cruiseOrderInputModel = CruiseOrderInputModel::query()
                                                      ->where('id', $id)
                                                      ->lockForUpdate()
                                                      ->first();

        // 校验字段是否存在
        list($inputType, $meanType, $projectObj) = $this->checkField($cruiseOrderInputModel, $field);
        if (!in_array($inputType, AttributeCruiseSupport::CRUISE_CHECK_MODULE)) {
            DB::rollBack();
            BaseService::throwErr("仅巡检项允许审核");
        }

        $userContentJsonArr = $cruiseOrderInputModel->userContentJsonArr;

        $userContentJsonArr[$field] = $value;

        // 保存auditValue字段
        $userContentJsonArr[$field . '_auditValue']['instructions'] = $instructions; // 用来判断督导是否操作过这个项目

        $cruiseOrderInputModel->userContentJson = json_encode($userContentJsonArr, true);

        $cruiseOrderInputModel->save();

        DB::commit();
    }

    /**
     * 巡检项基础校验
     * @param $value
     * @param array $auditValue
     * @return void
     */
    private function cruiseCheckValidate($value, array $auditValue): void
    {
        if ($value == AttributeCruiseSupport::CRUISE_CHECK_OK) {

            // 合格要有图片
            if (empty($auditValue['files'])) {
                DB::rollBack();
                BaseService::throwErr("合格，必须上传图片");
            }
        } elseif ($value == AttributeCruiseSupport::CRUISE_CHECK_FAIL) {

            $validator = Validator::make($auditValue, [
                'desc'                    => 'string', // 问题描述
                'files'                   => 'array',
                'rectifyStatus'           => 'integer|in:' . implode(',', array_keys(AttributeCruiseSupport::RECTIFY_STATUS_TEXT)),
                'rectifyFiles'            => 'required_if:rectifyStatus,' . AttributeCruiseSupport::RECTIFY_STATUS_DONE . '|array',
                'reason'                  => 'required_if:rectifyStatus,' . AttributeCruiseSupport::RECTIFY_STATUS_UNDONE . '|string', // 整改建议
            ], [
            'reason.required_if' => '您有必填项未填写'
            ]);

            if ($validator->fails()) {
                DB::rollBack();
                BaseService::throwErr($validator->errors()->first());
            }
        }
    }

    /**
     * 生物防治的校验和处理
     * @param $value
     * @param array $auditValue
     * @param int $meanType
     * @return array
     */
    private function biologyValidateAndHandle($value, array $auditValue, int $meanType): array
    {
        if ($value == AttributeCruiseSupport::CRUISE_CHECK_OK) {

            // 合格要有图片
            if (!isset($auditValue['files']) && $auditValue['files']) {
                DB::rollBack();
                BaseService::throwErr("生物防治合格，必须上传图片");
            }
        } elseif ($value == AttributeCruiseSupport::CRUISE_CHECK_FAIL) {

            $validator = Validator::make($auditValue, [
                'desc'                    => 'string', // 问题描述
                'files'                   => 'array',
                'riskType2'               => 'required|array',
                'riskType2.*'             => 'required|integer',
                'problems'                => 'required|array',
                'problems.*.riskType2'    => 'required|integer',
                'problems.*.problemIds'   => 'required|array',
                'problems.*.problemIds.*' => 'required|integer',
                'problems.*.elseProblem'  => 'string',
                'rectifyStatus'           => 'required|integer|in:' . implode(',',
                        array_keys(AttributeCruiseSupport::RECTIFY_STATUS_TEXT)),
                'rectifyFiles'            => 'required_if:rectifyStatus,' . AttributeCruiseSupport::RECTIFY_STATUS_DONE . '|array',
                'reason'                  => 'required_if:rectifyStatus,' . AttributeCruiseSupport::RECTIFY_STATUS_UNDONE . '|string', // 整改建议
                'remark'                  => 'string', // 备注
            ], [
                'reason.required_if' => '您有必填项未填写'
            ]);

            if ($validator->fails()) {
                DB::rollBack();
                BaseService::throwErr($validator->errors()->first());
            }

            if (count($auditValue['problems']) != count($auditValue['riskType2'])) {
                BaseService::throwErr("problems长度和riskType2不一致");
            }

            // 支持的riskType2
            $allowRiskType2 = CruiseCheckRiskProblemModel
                ::query()
                ->where('riskType1', $meanType)
                ->distinct()
                ->pluck('riskType2')
                ->toArray();

            // riskType2校验
            foreach ($auditValue['riskType2'] as $riskType2) {
                if (!in_array($riskType2, $allowRiskType2)) {
                    $name = CruiseCheckRiskProblemModel::getName('riskName2', $riskType2);
                    BaseService::throwErr("当前巡检项属于：{$name}, 不支持的风险类型：{$riskType2}");
                }
            }

            foreach ($auditValue['problems'] as $problem) {

                // riskType2支持的problems
                $allowProblemIds = CruiseCheckRiskProblemModel
                    ::query()
                    ->where('riskType2', $problem['riskType2'])
                    ->pluck('id')
                    ->toArray();

                if (!in_array($problem['riskType2'], $auditValue['riskType2'])) {
                    BaseService::throwErr("problems.riskType2:{$problem['riskType2']}，不在riskType2中");
                }

                // problemIds校验
                foreach ($problem['problemIds'] as $problemId) {
                    if (!in_array($problemId, $allowProblemIds)) {
                        $name = CruiseCheckRiskProblemModel::getName('problemName', $problemId);
                        BaseService::throwErr("当前二级分类：{$name}, 不支持的问题：{$problemId}");
                    }
                }
            }
        } elseif ($value == AttributeCruiseSupport::CRUISE_CHECK_SKIP) {
            $validator = Validator::make($auditValue, [
                'skipDesc' => 'string', // 不适用描述
            ]);

            if ($validator->fails()) {
                DB::rollBack();
                BaseService::throwErr($validator->errors()->first());
            }
        }

        return $auditValue;
    }

    /**
     * 获取项目input
     * @param CruiseOrderInputModel $cruiseOrderInputModel
     * @return array
     */
    protected function baseGetInputObjectFill(bool $disable = false): array
    {
        $cruiseOrderInputModel = CruiseOrderInputModel::getData($this->sysTemplate, $this->orderId);

        if (!$cruiseOrderInputModel) {
            return [
                'cruiseId' => $this->orderId,
                'lists'    => [],
            ];
        }

        $cruiseOrderInput = $cruiseOrderInputModel->inputObjectJsonArr;
        $userContentArr   = $cruiseOrderInputModel->userContentJsonArr;

        // 资源素材
        list($module, $business) = CruiseTemplateModel::TYPE_RESOURCE_PARAM[$this->templateType];

        $resource = (array)ResourcesModel::getModuleBusinessResources($module, $business,
            $cruiseOrderInputModel->orderId, []);

        foreach ($cruiseOrderInput as &$positions) {
            foreach ($positions['projects'] as &$project) {
                $projectObj = unserialize($project);

                $inputName = $projectObj->getInputName();
                $inputType = $projectObj->getInputType();

                if (isset($userContentArr[$inputName])) {
                    $projectObj->setValue($userContentArr[$inputName]);

                    // 巡检项的自定义字段
                    if (in_array($inputType, AttributeCruiseSupport::CRUISE_CHECK_MODULE)) {

                        if (isset($userContentArr[$inputName . '_auditValue'])) {

                            $auditValue = $userContentArr[$inputName . '_auditValue'];

                            // 兼容旧数据
                            if (!isset($auditValue['rectifyStatus'])) {
                                $auditValue['rectifyStatus'] = AttributeCruiseSupport::RECTIFY_STATUS_UNDONE;
                            }
                            if (!isset($auditValue['rectifyFiles'])) {
                                $auditValue['rectifyFiles'] = [];
                            }

                            $projectObj->setCustomAttribute(['auditValue', $auditValue]);
                        }

                        if (isset($userContentArr[$inputName . '_deductScore'])) {
                            $projectObj->setCustomAttribute([
                                'deductScore',
                                $userContentArr[$inputName . '_deductScore']
                            ]);
                        }
                    }
                } elseif (isset($resource[$inputName])) {
                    $projectObj->setValue($resource[$inputName]);
                } else {

                    // 默认返回空字符串不给null
                    if (in_array($inputType,
                        [AttributeSupport::INPUT_TYPE_STRING, AttributeSupport::INPUT_TYPE_TEXT])) {
                        $projectObj->setValue("");
                    }
                }

                // customAttribute特殊设置
                if ($inputType == AttributeCruiseSupport::INPUT_TYPE_FEE_DELAY) {

                    // 年费延期
                    $shopId = $this->getBaseCruiseOrderModel()->shopId;
                    $condition =  RenewService::getSingleton($shopId)->condition();

                    $projectObj->setCustomAttribute(['feeNum', $condition['feeNum']])
                               ->setCustomAttribute(['feeUrl', $condition['feeUrl']]);

                } elseif ($inputType == AttributeCruiseSupport::INPUT_TYPE_RECTIFY_AGAINST) {

                    // 违约函、整改单
                    $shopId    = $this->getBaseCruiseOrderModel()->shopId;
                    $condition = RenewService::getSingleton($shopId)->condition();

                    $projectObj->setCustomAttribute(['againstNum', $condition['againstNum']])
                               ->setCustomAttribute(['againstUrl', $condition['againstUrl']])
                               ->setCustomAttribute(['rectifyNum', $condition['rectifyNum']])
                               ->setCustomAttribute(['rectifyUrl', $condition['rectifyUrl']]);
                } elseif ($inputType == AttributeCruiseSupport::INPUT_TYPE_UPDATE_KF_SYSTEM) {

                    // 绑定夸父系统
                    $shopId    = $this->getBaseCruiseOrderModel()->shopId;
                    $condition = RenewService::getSingleton($shopId)->condition();

                    $tips = collect($projectObj->customAttributes['tips'])->map(function ($item) use ($condition) {
                        $item['isRenew'] = $condition[$item['column']] ?? false;
                        return $item;
                    })->toArray();

                    $projectObj->setCustomAttribute(['tips', $tips]);
                } elseif (in_array($inputType, [
                    AttributeCruiseSupport::INPUT_TYPE_IDENTITY_CARD_FRONT,
                    AttributeCruiseSupport::INPUT_TYPE_IDENTITY_CARD_BACK
                ])) {
                    $isPublic = $projectObj->customAttributes['isPublic'] ?? true;

                    // 私有图片处理
                    if (!$isPublic) {

                        $url = $projectObj->getValue()['url'] ?? '';
                        if ($url) {

                            $value = $projectObj->getValue();
                            $value['url'] = privateFilePath($url);
                            $projectObj->setValue($value);
                        }
                    }
                }

                // 没权限置为 - disable
                if ($disable) {
                    $projectObj->setCustomAttribute(["disabled", $disable]);
                }

                $project = $projectObj->build();
            }
        }

        return [
            'cruiseId' => $this->orderId,
            'lists'    => $cruiseOrderInput
        ];
    }

    /**
     * 用户提交表单
     * @return void
     */
    protected function baseSubmitOrder(bool $checkRequired = false): void
    {
        $cruiseOrderInputModel = CruiseOrderInputModel::getData($this->sysTemplate, $this->orderId);

        // 资源素材
        list($module, $business) = CruiseTemplateModel::TYPE_RESOURCE_PARAM[$this->templateType];

        $resource = (array)ResourcesModel::getModuleBusinessResources($module, $business,
            $cruiseOrderInputModel->orderId, []);

        // required校验
        if ($checkRequired) {
            $this->checkFieldRequired($cruiseOrderInputModel, $resource);

            // 特殊的inputType校验
            $this->checkInputType($cruiseOrderInputModel, $resource);
        }

        // 巡检项跳过 - 整个表单至少要有一个合格、不合格
        $this->checkCruiseCheckSkip($cruiseOrderInputModel);
    }

    private function checkCruiseCheckSkip($cruiseOrderInputModel): void
    {
        if ($this->templateType != CruiseTemplateModel::TYPE_8) {
            return;
        }

        // 至少有一个合格、不合格
        foreach ($cruiseOrderInputModel->inputObjectJsonArr as $projects) {
            foreach ($projects['projects'] as $project) {
                $projectObj = unserialize($project);
                $inputType  = $projectObj->getInputType();

                switch ($inputType) {
                    case AttributeCruiseSupport::INPUT_TYPE_CRUISE_CHECK:
                    case AttributeCruiseSupport::INPUT_TYPE_CRUISE_CHECK_SKIP:
                    case AttributeCruiseSupport::INPUT_TYPE_CRUISE_CHECK_BIOLOGY:

                        $value = $cruiseOrderInputModel->userContentJsonArr[$projectObj->getInputName()] ?? null;

                        // 有一个就返回
                        if (in_array($value, [AttributeCruiseSupport::CRUISE_CHECK_OK, AttributeCruiseSupport::CRUISE_CHECK_FAIL])) {
                            return;
                        }
                }
            }
        }

        BaseService::throwErr("不能全部跳过巡检项");
    }

    /**
     * 根据inputType特殊类型校验
     * @param $cruiseOrderInputModel
     * @param $resource
     * @return void
     */
    private function checkInputType($cruiseOrderInputModel, $resource)
    {
        foreach ($cruiseOrderInputModel->inputObjectJsonArr as $projects) {
            foreach ($projects['projects'] as $project) {
                $projectObj = unserialize($project);
                $inputType  = $projectObj->getInputType();

                switch ($inputType) {
                    case AttributeCruiseSupport::INPUT_TYPE_FEE_DELAY:

                        $shopId    = $this->getBaseCruiseOrderModel()->shopId;
                        $condition = RenewService::getSingleton($shopId)->condition();
                        if ($condition['feeNum'] > 0) {
                            BaseService::throwErr("有尚未处理的年费订单");
                        }

                        break;

                    case AttributeCruiseSupport::INPUT_TYPE_RECTIFY_AGAINST:
                        $shopId    = $this->getBaseCruiseOrderModel()->shopId;
                        $condition = RenewService::getSingleton($shopId)->condition();
                        if ($condition['againstNum'] > 0) {
                            BaseService::throwErr("有尚未处理的违约函");
                        }

                        if ($condition['rectifyNum'] > 0) {
                            BaseService::throwErr("有尚未处理的整改单");
                        }

                        break;

                    case AttributeCruiseSupport::INPUT_TYPE_UPDATE_KF_SYSTEM:

                        $value = $cruiseOrderInputModel->userContentJsonArr[$projectObj->getInputName()] ?? null;

                        // 兼容一个老问题
                        if ($value === null) {
                            $value = @$projectObj->value;
                        }

                        // 同意 - 需要完成三个更新
                        if ($value == AttributeCruiseSupport::CRUISE_CHECK_OK) {
                            // rms
                            $rms = RmsShopRemote::getShopDetailByShopId($this->getBaseCruiseOrderModel()->shopId);

                            // 夸父收银
                            $kfPos = $rms['fyOpenStatus'] ?? false;
                            if (!$kfPos) {
                                BaseService::throwErr("夸父收银未开通");
                            }

                            // 夸父监控
                            $kfMonitor = $rms['cameraOpenStatus'] ?? false;
                            if (!$kfMonitor) {
                                BaseService::throwErr("夸父监控未开通");
                            }

                            // 夸父商屏
                            $kfScreen = $rms['tvOpenStatus'] ?? false;
                            if (!$kfScreen) {
                                BaseService::throwErr("夸父商屏未开通");
                            }
                        }
                        break;
                }
            }
        }
    }

    /**
     * 校验field的required项
     * @param $cruiseOrderInputModel
     * @param $resource
     * @return void
     */
    private function checkFieldRequired($cruiseOrderInputModel, $resource): void
    {
        foreach ($cruiseOrderInputModel->inputObjectJsonArr as $projects) {
            foreach ($projects['projects'] as $project) {
                $projectObj = unserialize($project);

                // ------ 针对required的校验 ------

                // required
                $required = (bool)optional($projectObj->validateSupport)->getRequired();
                if (!$required) {
                    continue;
                }

                // 填了
                if (isset($cruiseOrderInputModel->userContentJsonArr[$projectObj->getInputName()])) {
                    continue;
                }

                // 填了
                if (isset($resource[$projectObj->getInputName()])) {
                    continue;
                }

                // 存在关联关系
                if ($projectObj->relations) {

                    // 关联配置
                    $relationField    = $projectObj->relations[0]['field'];
                    $relationValueArr = $projectObj->relations[0]['value'];

                    // 关联值
                    if (!isset($cruiseOrderInputModel->userContentJsonArr[$relationField])) {
                        continue;
                    }
                    $inputRelationValue = $cruiseOrderInputModel->userContentJsonArr[$relationField];

                    // 未命中关联值
                    if (!in_array($inputRelationValue, $relationValueArr) && $inputRelationValue != $relationValueArr) {
                        continue;
                    }
                }

                $inputDesc = $projectObj->inputDesc ?? '';
                throw new ResponseHttpException(BaseErrorCode::$ERROR_OCCURRED, null, "必填项未提交: {$inputDesc} {$projectObj->getInputName()}");
            }
        }
    }

    /**
     * 计算input得分
     * @param array $input
     * @return int
     */
    protected function calcOneInputScore(array $input): array
    {
        // 本题实际得分
        $actualScore = 0;

        // 本题总分
        $totalScore = 0;

        // 本题正确答案
        $correctValues = 0;

        if ($input['inputType'] == AttributeSupport::INPUT_TYPE_SELECT) {
            $options = collect($input['customAttributes']['options'])->sortByDesc('score')->keyBy('value');

            if (is_array($input['value']) && isset($input['value'][0])) {
                $value = $input['value'][0];
            } else {
                $value = $input['value'];
            }
            $actualScore   = ($options[$value]['score'] ?? 0);

            $totalScore    += ($options->first()['score']);
            $correctValues = (string)($options->first()['value'] ?? 0);

        } elseif ($input['inputType'] == AttributeSupport::INPUT_TYPE_SELECT_MULTIPLE) {

            // 当所有，有分数的项都选择了，才算得分
            $correctValues = collect($input['customAttributes']['options'])
                ->where('score', '>', 0)
                ->pluck('value')
                ->values()
                ->toArray();

            $submitValues = is_array($input['value']) ? array_values($input['value']) : [];
            sort($submitValues);

            if ($correctValues == $submitValues) {

                foreach ($submitValues as $v) {
                    $actualScore += (collect($input['customAttributes']['options'])
                        ->where('value', $v)
                        ->first()['score'] ?? 0);
                }
            }
            $totalScore += collect($input['customAttributes']['options'])->sum('score');
        }

        return [$actualScore, $totalScore, $correctValues];
    }

    /**
     * 将稽核相册的图片变为使用
     * @param array $auditValue
     * @param string $field
     * @return void
     */
    protected function albumUsed(string $field, array $auditValue, string $fileColumn): void
    {
        if (isset($auditValue[$fileColumn])) {

            // 将之前的变为未使用（如果有的话）
            $userContentJson = CruiseOrderInputModel::query()
                                                    ->where('orderId', $this->baseCruiseOrderModel->id)
                                                    ->where('templateType', $this->templateType)
                                                    ->value('userContentJson');

            $files = json_decode($userContentJson, true)[$field . '_auditValue'][$fileColumn] ?? [];

            if ($files) {
                $oldUrl = collect($files)->pluck('path')->toArray();
                CruiseCheckAlbumModel::query()
                                     ->whereIn('url', $oldUrl)
                                     ->update([
                                         'status' => CruiseCheckAlbumModel::STATUS_UNUSED
                                     ]);
            }

            // 变为已使用
            $url = [];
            foreach ($auditValue[$fileColumn] as $file) {
                $url[] = ltrim(parse_url($file['url'], PHP_URL_PATH), '/');
            }

            if ($url) {
                CruiseCheckAlbumModel::query()
                                     ->whereIn('url', $url)
                                     ->update([
                                         'status' => CruiseCheckAlbumModel::STATUS_USED
                                     ]);
            }

            if ($auditValue[$fileColumn]){
                AlbumResourceModel::query()
                                  ->whereIn('originalUrl', collect($auditValue[$fileColumn])->pluck("url")->toArray())
                                  ->update([
                                      'cruiseCheckStatus' => CruiseCheckAlbumModel::STATUS_USED
                                  ]);
            }
        }
    }

    /**
     * 平铺表单内容
     * @param string $key
     * @return array
     */
    public function inputArray(string $key = 'field'): array
    {
        $result = [];

        $lists = $this->getInputObjectFill()['lists'] ?? [];

        foreach ($lists as $position) {
            foreach ($position['projects'] as $project) {
                $result[$project[$key]] = $project;
            }
        }

        return $result;
    }
}
